package edu.csl.study.springboot.redis.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * 分布式锁 - redis实现
 * 
 * @author caoshouling
 * 
 *         分布式锁使用方法： try{ requestID = UUIDKit.generate();
 *         lock(T1_9010719244030000036,requestID,300) }finally{
 *         releaseLock(T1_9010719244030000036,requestID); }
 *
 */
@Component
public class RedisDistributedLock {

	private final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);

	@Autowired
	@Qualifier("redisTemplateForObject")
	private RedisTemplate<String, Object> redisTemplateForObject;

	public static final String UNLOCK_LUA;
	public static final String LOCK_KEY_PREFIX = "LOCK_";
	static {
		StringBuilder sb = new StringBuilder();
		sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
		sb.append("then ");
		sb.append("    return redis.call(\"del\",KEYS[1]) ");
		sb.append("else ");
		sb.append("    return 0 ");
		sb.append("end ");
		UNLOCK_LUA = sb.toString();
    }
    public boolean lock(String key, String requestID, long expireSeconds,String serverName) {
		return this.lock(key, requestID, expireSeconds, 0, 0,serverName);
	}

	public boolean lock(String key,String requestID, long expireSeconds, int retryTimes, long sleepMillis,String serverName) {
		boolean result = setRedis(key, requestID, expireSeconds);
		// 如果获取锁失败，按照传入的重试次数进行重试
		int i = 1;
		while ((!result) && retryTimes-- > 0) {
			try {
				logger.info("{}-{}-获取分布式锁失败，尝试第{}次重试.." ,key,serverName, i++);
				Thread.sleep(sleepMillis);
			} catch (InterruptedException e) {
				return false;
			}
			result = setRedis(key, requestID, expireSeconds);
		}
		logger.info("{}-{}-{}-获取分布式锁：{}",requestID,key,serverName,result);
		return result;
	}

	private boolean setRedis(String key, String requestID, long expireSeconds) {
		try {

			RedisCallback<Boolean> callback = (connection) -> connection.set((LOCK_KEY_PREFIX + key).getBytes(StandardCharsets.UTF_8),
					requestID.getBytes(StandardCharsets.UTF_8), Expiration.seconds(expireSeconds),
					RedisStringCommands.SetOption.SET_IF_ABSENT);
			return redisTemplateForObject.execute(callback);

		} catch (Exception e) {
			logger.error("{}-获取锁时发生异常！", key, e);
		}
		return false;
	}

	/**
	 *
	 * @param key
	 * @param requestID
	 * @param serverName
	 * @return
	 */
	public boolean releaseLock(String key,String requestID,String serverName) {
		// 释放锁的时候，有可能因为持锁之后方法执行时间大于锁的有效期，此时有可能已经被另外一个线程持有锁，所以不能直接删除
		boolean successFlag = false;
		try {
			// 使用lua脚本删除redis中匹配value的key，可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
			RedisCallback<Boolean> callback = (connection) -> connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN, 1, (LOCK_KEY_PREFIX + key).getBytes(Charset.forName("UTF-8")), requestID.getBytes(Charset.forName("UTF-8")));
			successFlag = (Boolean) redisTemplateForObject.execute(callback);

		} catch (Exception e) {
			logger.error("{}-{}-{}-释放锁时发生异常！",requestID,key,serverName, e);
		}
		logger.info("{}-{}-{}-释放锁：{}",requestID,key,serverName,successFlag);
		return successFlag;
	}

}
